/**
 * Copyright 2012 Eliot Stock
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.Random;

/**
 * Builds a URL for the Google Analytics urchin image which allows us to get data into GA from the
 * server side. Suitable for aggregate or sensitive data we may not want to expose to client side
 * HTML.
 * 
 * Usage:
 * String url = new AnalyticsRequestBuilder().account("UA-11111111-1").page("/").event(event).getUrl();
 * 
 * See also: https://developers.google.com/analytics/resources/articles/gaTrackingTroubleshooting#gifParameters
 */
public class AnalyticsRequestBuilder implements Serializable {
	
	private static final long serialVersionUID = 1L;
	
	private static final String GA_URL = "http://www.google-analytics.com/__utm.gif";
	
	private static final Random random = new Random();
	
	private static String machineHostName = "localhost";
	
	static {
		try {
			machineHostName = InetAddress.getLocalHost().getHostName();
		}
		catch (UnknownHostException e) {
			// Ignore
	    }
	}
	
	private static final String ENCODING = "ISO-8859-1";
	
	private String analyticsVersion = "5.3.5";
	
	// Should really increment for new requests within a single "page" but doesn't seem to matter.
	private Integer serialNumber = 1;
	
	private Integer randomNumber = random.nextInt();
	
	private String hostName = machineHostName;
	
	private AnalyticsEvent event;
	
	private AnalyticsCustomVariable customVariable;
	
	private String characterSet = "UTF-8";
	
	private String screenResolution = "1440x900";
	
	// Size of the HTML content. Screen size less all the chrome around the outside.
	private String viewPort = "1440x750";
	
	private String screenColorDepth = "24-bit";
	
	private String userLanguage = "en-us";
	
	private Boolean javaEnabled = true;
	
	private String flashVersion = "11.4 r402";
	
	private String documentTitle = "Server side data";
	
	private String referer = null;
	
	// Relative URL, eg: "/page.html". Required.
	private String page = null;
	
	// Required
	private String account = null;
	
	private Boolean campaignNew = null;
	
	private Boolean campaignRepeat = null;
	
	private Integer adSenseRandomNumber = null;
	
	private String cookie = null;
	
	public AnalyticsRequestBuilder() {
	}
	
	public AnalyticsRequestBuilder analyticsVersion(String analyticsVersion) {
		this.analyticsVersion = analyticsVersion;
		return this;
	}

	public AnalyticsRequestBuilder serialNumber(Integer serialNumber) {
		this.serialNumber = serialNumber;
		return this;
	}

	public AnalyticsRequestBuilder hostName(String hostName) {
		this.hostName = hostName;
		return this;
	}

	public AnalyticsRequestBuilder event(AnalyticsEvent event) {
		this.event = event;
		return this;
	}

	public AnalyticsRequestBuilder customVariable(AnalyticsCustomVariable customVariable) {
		this.customVariable = customVariable;
		return this;
	}

	public AnalyticsRequestBuilder characterSet(String characterSet) {
		this.characterSet = characterSet;
		return this;
	}

	public AnalyticsRequestBuilder screenResolution(String screenResolution) {
		this.screenResolution = screenResolution;
		return this;
	}

	public AnalyticsRequestBuilder viewPort(String viewPort) {
		this.viewPort = viewPort;
		return this;
	}

	public AnalyticsRequestBuilder screenColorDepth(String screenColorDepth) {
		this.screenColorDepth = screenColorDepth;
		return this;
	}

	public AnalyticsRequestBuilder userLanguage(String userLanguage) {
		this.userLanguage = userLanguage;
		return this;
	}

	public AnalyticsRequestBuilder javaEnabled(Boolean javaEnabled) {
		this.javaEnabled = javaEnabled;
		return this;
	}

	public AnalyticsRequestBuilder flashVersion(String flashVersion) {
		this.flashVersion = flashVersion;
		return this;
	}

	public AnalyticsRequestBuilder documentTitle(String documentTitle) {
		this.documentTitle = documentTitle;
		return this;
	}

	public AnalyticsRequestBuilder referer(String referer) {
		this.referer = referer;
		return this;
	}

	public AnalyticsRequestBuilder page(String page) {
		this.page = page;
		return this;
	}

	public AnalyticsRequestBuilder account(String account) {
		this.account = account;
		return this;
	}

	public AnalyticsRequestBuilder campaignNew(Boolean campaignNew) {
		this.campaignNew = campaignNew;
		return this;
	}

	public AnalyticsRequestBuilder campaignRepeat(Boolean campaignRepeat) {
		this.campaignRepeat = campaignRepeat;
		return this;
	}

	public AnalyticsRequestBuilder adSenseRandomNumber(Integer adSenseRandomNumber) {
		this.adSenseRandomNumber = adSenseRandomNumber;
		return this;
	}

	public AnalyticsRequestBuilder setCookie(String cookie) {
		this.cookie = cookie;
		return this;
	}

	public String getUrl() {
		checkState();
		
		StringBuffer url = new StringBuffer(GA_URL);
		
		url.append("?");
		
		url.append("utmwv=");
		url.append(analyticsVersion);
		url.append("&");
		
		url.append("utms=");
		url.append(serialNumber);
		url.append("&");
		
		url.append("utmn=");
		url.append(randomNumber);
		url.append("&");
		
		url.append("utmhn=");
		url.append(hostName);
		url.append("&");
		
		if (event != null) {
			// Format: 5(category*action*label)(value)
			
			url.append("utmt=event");
			url.append("&");
			
			url.append("utme=");
			url.append("5(");
			url.append(urlEncode(event.getCategory()));
			url.append("*");
			url.append(urlEncode(event.getAction()));
			
			if (event.getLabel() != null) {
				url.append("*");
				url.append(urlEncode(event.getLabel()));
			}
			
			url.append(")");			
			
			if (event.getValue() != null) {
				url.append("(");
				url.append(urlEncode(event.getValue().toString()));
				url.append(")");
			}
			
			url.append("&");			
		}
		else if (customVariable != null) {
			// Format: 8(name)9(value)11(scope)
			
			// We don't currently use the index (slot) on a custom variable.
			
			// Don't set utmt (type) for custom variables. Let it default to page.
			
			url.append("utme=");
			url.append("8(");
			url.append(urlEncode(customVariable.getName()));
			url.append(")");
			url.append("9(");
			url.append(urlEncode(customVariable.getValue()));
			url.append(")");
			url.append("11(");
			url.append(urlEncode(customVariable.getScope().toString()));
			url.append(")");
			url.append("&");		
		}
		
		url.append("utmcs=");
		url.append(characterSet);
		url.append("&");
		
		url.append("utmsr=");
		url.append(screenResolution);
		url.append("&");
		
		url.append("utmvp=");
		url.append(viewPort);
		url.append("&");
		
		url.append("utmsc=");
		url.append(screenColorDepth);
		url.append("&");
		
		url.append("utmul=");
		url.append(userLanguage);
		url.append("&");
		
		url.append("utmje=");
		if (javaEnabled != null && javaEnabled) {
			url.append("1");			
		}
		else {
			url.append("0");						
		}
		url.append("&");
		
		url.append("utmfl=");
		url.append(urlEncode(flashVersion));
		url.append("&");
		
		url.append("utmdt=");
		url.append(documentTitle);
		url.append("&");
		
		if (adSenseRandomNumber != null) {
			url.append("utmhid=");
			url.append(adSenseRandomNumber);
			url.append("&");			
		}
		
		url.append("utmr=");
		if (referer != null) {
			url.append(referer);			
		}
		else {
			url.append("-");			
		}
		url.append("&");
		
		url.append("utmp=");
		url.append(urlEncode(page));
		url.append("&");
		
		if (campaignNew != null && campaignNew) {
			url.append("utmcn=");
			url.append("1");
			url.append("&");			
		}
		else if (campaignRepeat != null && campaignRepeat) {
			url.append("utmcr=");
			url.append("1");
			url.append("&");
		}
		
		if (cookie != null) {
			url.append("utmcc=");
			url.append(urlEncode(cookie));
			url.append("&");			
		}
		
		url.append("utmac=");
		url.append(account);
		
		return url.toString();
	}

	private void checkState() {
		if (account == null) {
			throw new IllegalStateException("Account is required.");
		}
		
		if (page == null) {
			throw new IllegalStateException("Page is required.");
		}
		
		if (event != null && customVariable != null) {
			throw new IllegalStateException("Set either event or customVariable, but not both." +
					" Putting both into one request is possible for GA, but we don't support" +
					" it yet on our side.");
		}
		
		if (campaignNew != null && campaignRepeat != null && campaignNew && campaignRepeat) {
			throw new IllegalStateException("Campaign is either new or repeat. Don't set both" +
					" campaignNew and campaignRepeat");
		}
	}
	
	private String urlEncode(String value) {
		try {
			return URLEncoder.encode(value, ENCODING);
		}
		catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

}
